package com.pine.dbant.common.jdbc;


import com.pine.dbant.common.jdbc.core.*;

import java.sql.*;
import java.util.List;
import java.util.Map;

/**
 * @author xiaoyuan
 * @create 2022/6/30 17:44
 **/
public class SqlRunnerImpl implements SqlRunner {

    private Connection connection;

    private Integer timeout;

    private Integer fetchSize;


    public SqlRunnerImpl(Connection connection) {
        this.connection = connection;
    }

    @Override
    public <T> T selectOne(String sql, Class<T> tClass, Object... params) {
        return query(sql, new SingleRowRecordMapper<>(new BeanRowMapper<>(tClass)), params);
    }

    @Override
    public Map<String, Object> selectOne(String sql, Object... params) {
        return query(sql, new SingleRowRecordMapper<>(new MapRowMapper()), params);
    }

    @Override
    public <T> List<T> selectList(String sql, Class<T> type, Object... params) {
        return query(sql, new ListRecordMapper<>(new BeanRowMapper<>(type)), params);
    }

    @Override
    public <T> List<T> queryList(String sql, RowMapper<T> rowMapper, Object... params) {
        return query(sql, new ListRecordMapper<>(rowMapper), params);
    }

    @Override
    public List<Map<String, Object>> queryList(String sql, Object... params) {
        return query(sql, new ListRecordMapper<>(new MapRowMapper()), params);
    }

    @Override
    public Record query(String sql, Object... params) {
        ResultSet rs = null;
        PreparedStatement stmt = null;
        try {
            stmt = createPreparedStatement(connection, sql, params);
            rs = stmt.executeQuery();
            return new RecordAdapterForResultSet(rs);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        } finally {
//            close(stmt, rs);
        }
    }

    @Override
    public boolean execute(String sql, Boolean autoCommit, Object... params) {
        return autoCommit ? executeToAutoCommit(sql, params) : executeToNoAutoCommit(sql, params);
    }


    private boolean executeToAutoCommit(String sql, Object... params) {
        PreparedStatement stmt = null;
        try {
            this.startTransaction();
            stmt = createPreparedStatement(connection, sql, params);
            boolean result = stmt.execute();
            this.commit();
            return result;
        } catch (Exception e) {
            this.rollback();
            throw new RuntimeException(e.getMessage(), e);
        } finally {
            close(null, stmt);
        }
    }

    private boolean executeToNoAutoCommit(String sql, Object... params) {
        PreparedStatement stmt = null;
        try {
            stmt = createPreparedStatement(connection, sql, params);
            return stmt.execute();
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        } finally {
            close(null, stmt);
        }
    }

    private void validateConn() {
        try {
            if (connection == null || connection.isClosed()) {
                throw new RuntimeException("该连接已经关闭，无法执行");
            }
        } catch (SQLException e) {
            throw new RuntimeException("该连接已经关闭，无法执行");
        }
    }

    @Override
    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    @Override
    public void setFetchSize(Integer fetchSize) {
        this.fetchSize = fetchSize;
    }

    @Override
    public void startTransaction() {
        if (this.connection != null) {
            try {
                this.connection.setAutoCommit(false);
            } catch (SQLException e) {
                throw new RuntimeException("An error occurred while starting transaction.", e);
            }
        }
    }

    @Override
    public void commit() {
        if (this.connection != null) {
            try {
                this.connection.commit();
                this.connection.setAutoCommit(true);
            } catch (SQLException e) {
                throw new RuntimeException("An error occurred while committing transaction.", e);
            }
        }
    }

    @Override
    public void rollback() {
        if (this.connection != null) {
            try {
                this.connection.rollback();
                this.connection.setAutoCommit(true);
            } catch (SQLException e) {
                throw new RuntimeException("An error occurred while committing transaction.", e);
            }
        }
    }

    @Override
    public void close() {
        try {
            this.connection.close();
            this.connection = null;
        } catch (SQLException e) {

        }
    }

    /**
     * 查询数据库并转换结果集。
     * 用户可自定义结果集转换器。
     * 用户也可使用预定义的结果集转换器。
     *
     * @param sql          sql语句
     * @param recordMapper 结果集转换器
     * @param params       sql参数
     * @param <T>          resultSetMapper返回的结果类型
     * @return 成功则返回转换结果，失败则抛出DbException，结果为空则返回空列表
     * @see RecordMapper
     */
    public <T> T query(String sql, RecordMapper<T> recordMapper, Object... params) {
        ResultSet rs = null;
        PreparedStatement stmt = null;
        try {
            stmt = createPreparedStatement(connection, sql, params);
            rs = stmt.executeQuery();
            return recordMapper.map(new RecordAdapterForResultSet(rs));
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage(), e);
        } finally {
            close(stmt, rs);
        }
    }

    /**
     * 创建语句
     *
     * @param conn   连接
     * @param sql    sql语句
     * @param params sql参数
     * @return 创建的PreparedStatement对象
     * @throws SQLException 来自JDBC的异常
     */
    private PreparedStatement createPreparedStatement(Connection conn, String sql, Object... params) throws SQLException {
        return this.createPreparedStatement(conn, sql, this.timeout, params);
    }

    /**
     * 创建语句
     *
     * @param conn   连接
     * @param sql    sql语句
     * @param params sql参数
     * @return 创建的PreparedStatement对象
     * @throws SQLException 来自JDBC的异常
     */
    private PreparedStatement createPreparedStatement(Connection conn, String sql, Integer timeout, Object... params) throws SQLException {
        validateConn();
        PreparedStatement stmt = conn.prepareStatement(sql);
        for (int i = 0; i < params.length; ++i) {
            stmt.setObject(i + 1, params[i]);
        }
        if (timeout != null) {
            stmt.setQueryTimeout(timeout);
        }
        return stmt;
    }

    public void close(Connection conn, Statement stmt) {
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException ignored) {
            }
        }
        if (conn != null) {
            try {
                if (conn.getAutoCommit()) {
                    conn.close();
                }
            } catch (SQLException ignored) {
            }
        }
    }

    public void close(Connection conn, Statement stmt, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException ignored) {
            }
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException ignored) {
            }
        }
        if (conn != null) {
            try {
                if (conn.getAutoCommit()) {
                    conn.close();
                }
            } catch (SQLException ignored) {
            }
        }
    }

    public void close(Statement stmt, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (SQLException ignored) {
            }
        }
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException ignored) {
            }
        }
    }
}
